/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included  with this distribution in
 * the LICENSE.txt file.
 */

package biz.almas.uycnr.crawler.utils;

import java.net.URL;
import java.util.Properties;

import org.apache.log4j.Level;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.spi.Configurator;
import org.apache.log4j.spi.LoggerRepository;

// Contributors: Avy Sharell (sharell@online.fr)
// Matthieu Verbert (mve@zurich.ibm.com)
// Colin Sampaleanu

/**
 * A convenience class to convert property values to specific types.
 * 
 * @author Ceki G&uuml;lc&uuml;
 * @author Simon Kitching;
 * @author Anders Kristensen
 */
public class OptionConverter {
    private static org.apache.log4j.Logger LogLog = org.apache.log4j.Logger
            .getLogger(OptionConverter.class);

    static String DELIM_START = "${";

    static char DELIM_STOP = '}';

    static int DELIM_START_LEN = 2;

    static int DELIM_STOP_LEN = 1;

    /** OptionConverter is a static class. */
    private OptionConverter() {
    }

    public static String[] concatanateArrays(String[] l, String[] r) {
        int len = l.length + r.length;
        String[] a = new String[len];

        System.arraycopy(l, 0, a, 0, l.length);
        System.arraycopy(r, 0, a, l.length, r.length);

        return a;
    }

    public static String convertSpecialChars(String s) {
        char c;
        int len = s.length();
        StringBuffer sbuf = new StringBuffer(len);

        int i = 0;
        while (i < len) {
            c = s.charAt(i++);
            if (c == '\\') {
                c = s.charAt(i++);
                if (c == 'n')
                    c = '\n';
                else if (c == 'r')
                    c = '\r';
                else if (c == 't')
                    c = '\t';
                else if (c == 'f')
                    c = '\f';
                else if (c == '\b')
                    c = '\b';
                else if (c == '\"')
                    c = '\"';
                else if (c == '\'')
                    c = '\'';
                else if (c == '\\')
                    c = '\\';
            }
            sbuf.append(c);
        }
        return sbuf.toString();
    }

    /**
     * Very similar to <code>System.getProperty</code> except that the
     * {@link SecurityException} is hidden.
     * 
     * @param key
     *            The key to search for.
     * @param def
     *            The default value to return.
     * @return the string value of the system property, or the default value if
     *         there is no property with that key.
     * 
     * @since 1.1
     */
    public static String getSystemProperty(String key, String def) {
        try {
            return System.getProperty(key, def);
        } catch (Throwable e) { // MS-Java throws
            // com.ms.security.SecurityExceptionEx
            LogLog.debug("Was not allowed to read system property \"" + key
                    + "\".");
            return def;
        }
    }

    public static Object instantiateByKey(Properties props, String key,
            Class superClass, Object defaultValue) {

        // Get the value of the property in string form
        String className = findAndSubst(key, props);
        if (className == null) {
            LogLog.error("Could not find value for key " + key);
            return defaultValue;
        }
        // Trim className to avoid trailing spaces that cause problems.
        return OptionConverter.instantiateByClassName(className.trim(),
                superClass, defaultValue);
    }

    /**
     * If <code>value</code> is "true", then <code>true</code> is returned.
     * If <code>value</code> is "false", then <code>true</code> is returned.
     * Otherwise, <code>default</code> is returned.
     * 
     * <p>
     * Case of value is unimportant.
     */
    public static boolean toBoolean(String value, boolean dEfault) {
        if (value == null)
            return dEfault;
        String trimmedVal = value.trim();
        if ("true".equalsIgnoreCase(trimmedVal))
            return true;
        if ("false".equalsIgnoreCase(trimmedVal))
            return false;
        return dEfault;
    }

    public static int toInt(String value, int dEfault) {
        if (value != null) {
            String s = value.trim();
            try {
                return Integer.valueOf(s).intValue();
            } catch (NumberFormatException e) {
                LogLog.error("[" + s + "] is not in proper int form.");
                e.printStackTrace();
            }
        }
        return dEfault;
    }

    /**
     * Converts a standard or custom priority level to a Level object.
     * <p>
     * If <code>value</code> is of form "level#classname", then the specified
     * class' toLevel method is called to process the specified level string; if
     * no '#' character is present, then the default
     * {@link org.apache.log4j.Level} class is used to process the level value.
     * 
     * <p>
     * As a special case, if the <code>value</code> parameter is equal to the
     * string "NULL", then the value <code>null</code> will be returned.
     * 
     * <p>
     * If any error occurs while converting the value to a level, the
     * <code>defaultValue</code> parameter, which may be <code>null</code>,
     * is returned.
     * 
     * <p>
     * Case of <code>value</code> is insignificant for the level level, but is
     * significant for the class name part, if present.
     * 
     * @since 1.1
     */
    public static Level toLevel(String value, Level defaultValue) {
        if (value == null)
            return defaultValue;

        int hashIndex = value.indexOf('#');
        if (hashIndex == -1) {
            if ("NULL".equalsIgnoreCase(value)) {
                return null;
            }
            // no class name specified : use standard Level class
            return Level.toLevel(value, defaultValue);

        }

        Level result = defaultValue;

        String clazz = value.substring(hashIndex + 1);
        String levelName = value.substring(0, hashIndex);

        // This is degenerate case but you never know.
        if ("NULL".equalsIgnoreCase(levelName)) {
            return null;
        }

        LogLog.debug("toLevel" + ":class=[" + clazz + "]" + ":pri=["
                + levelName + "]");

        try {
            Class customLevel = Loader.loadClass(clazz);

            // get a ref to the specified class' static method
            // toLevel(String, org.apache.log4j.Level)
            Class[] paramTypes = new Class[] { String.class,
                    org.apache.log4j.Level.class };
            java.lang.reflect.Method toLevelMethod = customLevel.getMethod(
                    "toLevel", paramTypes);

            // now call the toLevel method, passing level string + default
            Object[] params = new Object[] { levelName, defaultValue };
            Object o = toLevelMethod.invoke(null, params);

            result = (Level) o;
        } catch (ClassNotFoundException e) {
            LogLog.warn("custom level class [" + clazz + "] not found.");
        } catch (NoSuchMethodException e) {
            LogLog
                    .warn(
                            "custom level class ["
                                    + clazz
                                    + "]"
                                    + " does not have a constructor which takes one string parameter",
                            e);
        } catch (java.lang.reflect.InvocationTargetException e) {
            LogLog.warn("custom level class [" + clazz + "]"
                    + " could not be instantiated", e);
        } catch (ClassCastException e) {
            LogLog.warn("class [" + clazz
                    + "] is not a subclass of org.apache.log4j.Level", e);
        } catch (IllegalAccessException e) {
            LogLog.warn("class [" + clazz
                    + "] cannot be instantiated due to access restrictions", e);
        } catch (Exception e) {
            LogLog.warn("class [" + clazz + "], level [" + levelName
                    + "] conversion failed.", e);
        }
        return result;
    }

    public static long toFileSize(String value, long dEfault) {
        if (value == null)
            return dEfault;

        String s = value.trim().toUpperCase();
        long multiplier = 1;
        int index;

        if ((index = s.indexOf("KB")) != -1) {
            multiplier = 1024;
            s = s.substring(0, index);
        } else if ((index = s.indexOf("MB")) != -1) {
            multiplier = 1024 * 1024;
            s = s.substring(0, index);
        } else if ((index = s.indexOf("GB")) != -1) {
            multiplier = 1024 * 1024 * 1024;
            s = s.substring(0, index);
        }
        if (s != null) {
            try {
                return Long.valueOf(s).longValue() * multiplier;
            } catch (NumberFormatException e) {
                LogLog.error("[" + s + "] is not in proper int form.");
                LogLog.error("[" + value + "] not in expected format.", e);
            }
        }
        return dEfault;
    }

    /**
     * Find the value corresponding to <code>key</code> in <code>props</code>.
     * Then perform variable substitution on the found value.
     * 
     */
    public static String findAndSubst(String key, Properties props) {
        String value = props.getProperty(key);
        if (value == null)
            return null;

        try {
            return substVars(value, props);
        } catch (IllegalArgumentException e) {
            LogLog.error("Bad option value [" + value + "].", e);
            return value;
        }
    }

    /**
     * Instantiate an object given a class name. Check that the
     * <code>className</code> is a subclass of <code>superClass</code>. If
     * that test fails or the object could not be instantiated, then
     * <code>defaultValue</code> is returned.
     * 
     * @param className
     *            The fully qualified class name of the object to instantiate.
     * @param superClass
     *            The class to which the new object should belong.
     * @param defaultValue
     *            The object to return in case of non-fulfillment
     */
    public static Object instantiateByClassName(String className,
            Class superClass, Object defaultValue) {
        if (className != null) {
            try {
                Class classObj = Loader.loadClass(className);
                if (!superClass.isAssignableFrom(classObj)) {
                    LogLog.error("A \"" + className
                            + "\" object is not assignable to a \""
                            + superClass.getName() + "\" variable.");
                    LogLog.error("The class \"" + superClass.getName()
                            + "\" was loaded by ");
                    LogLog.error("[" + superClass.getClassLoader()
                            + "] whereas object of type ");
                    LogLog.error("\"" + classObj.getName()
                            + "\" was loaded by [" + classObj.getClassLoader()
                            + "].");
                    return defaultValue;
                }
                return classObj.newInstance();
            } catch (Exception e) {
                LogLog.error(
                        "Could not instantiate class [" + className + "].", e);
            }
        }
        return defaultValue;
    }

    /**
     * Perform variable substitution in string <code>val</code> from the
     * values of keys found in the system propeties.
     * 
     * <p>
     * The variable substitution delimeters are <b>${</b> and <b>}</b>.
     * 
     * <p>
     * For example, if the System properties contains "key=value", then the call
     * 
     * <pre>
     * String s = OptionConverter.substituteVars(&quot;Value of key is ${key}.&quot;);
     * </pre>
     * 
     * will set the variable <code>s</code> to "Value of key is value.".
     * 
     * <p>
     * If no value could be found for the specified key, then the
     * <code>props</code> parameter is searched, if the value could not be
     * found there, then substitution defaults to the empty string.
     * 
     * <p>
     * For example, if system propeties contains no value for the key
     * "inexistentKey", then the call
     * 
     * <pre>
     * String s = OptionConverter
     *         .subsVars(&quot;Value of inexistentKey is [${inexistentKey}]&quot;);
     * </pre>
     * 
     * will set <code>s</code> to "Value of inexistentKey is []"
     * 
     * <p>
     * An {@link java.lang.IllegalArgumentException} is thrown if
     * <code>val</code> contains a start delimeter "${" which is not balanced
     * by a stop delimeter "}".
     * </p>
     * 
     * <p>
     * <b>Author</b> Avy Sharell</a>
     * </p>
     * 
     * @param val
     *            The string on which variable substitution is performed.
     * @throws IllegalArgumentException
     *             if <code>val</code> is malformed.
     * 
     */
    public static String substVars(String val, Properties props)
            throws IllegalArgumentException {

        StringBuffer sbuf = new StringBuffer();

        int i = 0;
        int j, k;

        while (true) {
            j = val.indexOf(DELIM_START, i);
            if (j == -1) {
                // no more variables
                if (i == 0) { // this is a simple string
                    return val;
                }
                // add the tail string which contails no variables
                // and return the result.
                sbuf.append(val.substring(i, val.length()));
                return sbuf.toString();

            }
            sbuf.append(val.substring(i, j));
            k = val.indexOf(DELIM_STOP, j);
            if (k == -1) {
                throw new IllegalArgumentException('"' + val
                        + "\" has no closing brace. Opening brace at position "
                        + j + '.');
            }
            j += DELIM_START_LEN;
            String key = val.substring(j, k);
            // first try in System properties
            String replacement = getSystemProperty(key, null);
            // then try props parameter
            if (replacement == null && props != null) {
                replacement = props.getProperty(key);
            }

            if (replacement != null) {
                // Do variable substitution on the replacement string
                // such that we can solve "Hello ${x2}" as "Hello p1"
                // the where the properties are
                // x1=p1
                // x2=${x1}
                String recursiveReplacement = substVars(replacement, props);
                sbuf.append(recursiveReplacement);
            }
            i = k + DELIM_STOP_LEN;

        }
    }

    /**
     * Configure log4j given a URL.
     * 
     * <p>
     * The url must point to a file or resource which will be interpreted by a
     * new instance of a log4j configurator.
     * 
     * <p>
     * All configurations steps are taken on the <code>hierarchy</code> passed
     * as a parameter.
     * 
     * <p>
     * 
     * @param url
     *            The location of the configuration file or resource.
     * @param clazz
     *            The classname, of the log4j configurator which will parse the
     *            file or resource at <code>url</code>. This must be a
     *            subclass of {@link Configurator}, or null. If this value is
     *            null then a default configurator of
     *            {@link PropertyConfigurator} is used, unless the filename
     *            pointed to by <code>url</code> ends in '.xml', in which case
     *            {@link org.apache.log4j.xml.DOMConfigurator} is used.
     * @param hierarchy
     *            The {@link org.apache.log4j.Hierarchy} to act on.
     * 
     * @since 1.1.4
     */
    static public void selectAndConfigure(URL url, String clazz,
            LoggerRepository hierarchy) {
        Configurator configurator = null;
        String filename = url.getFile();

        if (clazz == null && filename != null && filename.endsWith(".xml")) {
            clazz = "org.apache.log4j.xml.DOMConfigurator";
        }

        if (clazz != null) {
            LogLog.debug("Preferred configurator class: " + clazz);
            configurator = (Configurator) instantiateByClassName(clazz,
                    Configurator.class, null);
            if (configurator == null) {
                LogLog.error("Could not instantiate configurator [" + clazz
                        + "].");
                return;
            }
        } else {
            configurator = new PropertyConfigurator();
        }

        configurator.doConfigure(url, hierarchy);
    }
}
